package cn.jesin.css.filter;

import cn.hutool.core.util.StrUtil;
import cn.jesin.css.utils.JwtTokenUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * @author 谷汉斌
 * @description 用于验证每一次请求的身份信息
 * <p>
 * OncePerRequestFilter
 * 是Spring框架中的一种过滤器，它确保每个请求最多处理一次。
 * JwtAuthenticationTokenFilter继承自它，意味着它也会在每个请求中执行一次过滤逻辑。
 * <p>
 * UsernamePasswordAuthenticationToken
 * 是Spring Security框架中用于封装用户名、密码和权限信息的一个类，主要用于认证过程。
 * 当你需要创建一个认证请求或者响应时，通常会用到这个类
 * Authentication接口，用于封装用户的认证信息。这个类主要由以下三个参数构成：
 * Principal (主体Object类): 通常是用户的用户名，但在某些情况下也可以是其他形式的身份标识。
 * Credentials (凭证Object类): 通常是用户的密码，但在某些情况下也可以是其他形式的认证信息，如令牌。
 * Authorities (权限List类): 用户拥有的权限列表，表示用户可以执行的操作。
 * <p>
 * SecurityContextHolder
 * 是 Spring Security 框架中的一个核心组件，它负责管理当前线程的安全上下文（SecurityContext）。
 * 在 Spring Security 中，安全上下文存储了当前用户的身份认证信息（Authentication 对象），包括用户身份、权限等。
 * SecurityContextHolder 提供了一种线程绑定的方式去访问 SecurityContext，这意味着每个线程都有自己的安全上下文，
 * 这在多线程环境中特别重要，以避免不同用户请求之间的安全上下文冲突。
 * getContext(): 获取当前线程的安全上下文。这个方法返回的是 SecurityContext 的实例。
 * setContext(SecurityContext context): 设置当前线程的安全上下文。这通常在认证成功后调用，将认证信息放入上下文中。
 * clearContext(): 清除当前线程的安全上下文。这通常在用户登出或请求结束时调用。
 * getAuthentication(): 这不是一个直接在 SecurityContextHolder 上的方法，但可以通过 getContext().getAuthentication() 调用来获取当前线程的安全上下文中存储的 Authentication 对象。
 * setAuthentication(Authentication auth): 同样不是直接在 SecurityContextHolder 上的方法，而是通过 getContext().setAuthentication(auth) 调用来设置当前线程的安全上下文中的 Authentication 对象。
 * @createTime 2024/7/23 上午9:26
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    /**
     * @param request  请求
     * @param response 响应
     * @param chain    过滤器链
     * @description 对每一个请求进行（过滤）身份验证
     */
    @SneakyThrows({ServletException.class, IOException.class})
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        log.info("开始执行JwtAuthenticationTokenFilter*************");
        // 从请求头中获取token
        String token = request.getHeader(JwtTokenUtil.getHeader());
        if (StrUtil.isNotBlank(token)) {
            log.info("验证token{}", token);
            // 从token中获取唯一标识
            String key = jwtTokenUtil.getKeyFromToken(token);
            // 判断用户名是否为空，且当前用户登录线程上下文为空（第一次登录）
            if (null != key && null == SecurityContextHolder.getContext().getAuthentication()) {
                // 1.获取用户信息（JwtUserDetailsServiceImpl子类实现）
                UserDetails userDetails = userDetailsService.loadUserByUsername(key);
                // 2.校验token
                if (jwtTokenUtil.validateToken(token, userDetails)) {
                    // 将用户信息存入 authentication，方便后续校验
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    // 这段代码的作用是将当前的Web请求细节设置到认证对象中，以便后续的身份验证和授权操作可以使用这些细节。
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // 将 authentication 存入当前登录线程的安全上下文，方便后续获取用户信息
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}